import thread, time,  sys, pdb, smtplib, re, random, os, fileinput
from string import digits
from string import ascii_letters
from os import mkdir
import ast #for ast.literal_eval(protect against code injection)
import socket
from time import gmtime, strftime

#using Json for humen-readable database
HostSer='192.168.1.227'                             
PortSer= 5006                              

messages = {}
UsrSock = {}

IpClient = 0
rec = ''
currentUser = "" #the email to be identify

sckobj = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sckobj.bind((HostSer,PortSer))
sckobj.listen(3)          
print "%d..." %(PortSer)               

def buildMsg(msg):
    return str(msg) + "\x00" * (1000 - len(msg))
    
def emailCheck(conn):
        #
        #    This function will check if the email located in the database
        #    read only 
        #
        
        fp=open("C:\\Users\\Dvir\\Dropbox\\chat\\cred.txt","r")
        try:
            string=fp.read()

        except:
            string=""
        
        fp.close()
        return string
  
''' 
def Validiation(name,familyname,email):
    #
    #   A valid name or family name is only a-f or A-F
    #
    
    #pdb.set_trace()
    if len(email) > 7 and name != "" and familyname != "":
        if re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", email) != None: 
            if re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", name) == None: 
                if re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", familyname) == None:
                    return 1
    return 0
        
'''
def Register(cred, name,familyname,email,conn):

    cred2 = {}
    #To do: function that will check if the email entered is valid and name and familyname
    #def validiation
    #pdb.set_trace()
    i = 0
    #pdb.set_trace()
    #pdb.set_trace()

        
    while 1: #  issue could be exploited in such a way of the name or the familyname will be the same as  the mail this code will check if there is a email as same as entered
        try:
            emails=cred[i].values()
        except:
            break
            
        if (email in emails):
            return 2            
        
        i += 1
            
   # pdb.set_trace()
    password=""
    b=digits+ascii_letters
    for i in range (0,9):
        password=password+random.choice(b)
        
        
    if(type(cred)== tuple):    
        cred2["name"] = name
        cred2["pass"]= password
        cred2["email"] = email
        cred2["familyname"] = familyname    
        DeconstructCred(cred2)

    else:
        cred["name"] = name
        cred["pass"]= password
        cred["email"] = email
        cred["familyname"] = familyname
        DeconstructCred(cred)
    
        
    #pdb.set_trace()
    session = smtplib.SMTP('smtp.gmail.com', 587)
    session.ehlo()
    session.starttls()
    session.ehlo()
    session.login('char0program@gmail.com', 'pass123456789')
    body="Dear "+ name+" " +familyname+ "\nYour password is: "+password
    headers=["from: " + 'char0program@gmail.com',  "subject: " + "Register to D&M chat", "to: " + email,"content-type: text/html"]
    headers="\r\n".join(headers)
    try:
        session.sendmail('char0program@gmail.com', email,headers+"\r\n\r\n" +body)
        return email
    except :
        print "invalid mail"
        conn.send("wrong")
        return 0
    #pdb.set_trace()    

    return 0  
    
    
def sendFriend(conn, result, friendList):
    #pdb.set_trace()
    for k,v in friendList[result].iteritems():
        conn.send(buildMsg(k))
        time.sleep(0.5)
        conn.send(buildMsg(v))
        time.sleep(0.5)
    conn.send("0")
 
def Registereds(cred):
    k=0
    registeredUsers = []
    while 1:
        try:
            registeredUsers.append(cred[k]["email"])
        except:
            break
        k+=1
    return registeredUsers
    
def resetPassword(cred,conn, result):
    i = 0
    j=0
    isIn = []
    prevCred = {}
    #pdb.set_trace()
    while 1:
        try:
            if(cred[i]["email"] == result):
                break
        except:
            break
        i+=1
    
    isIn = Registereds(cred)
    if result not in isIn:
        conn.send("wrong")
        return 
        
    password=""
    b=digits+ascii_letters
    for j in range (0,9):
        password=password+random.choice(b)
        
    #pdb.set_trace()    
    cred[i]["pass"] = password
    #Handle multiple write to the database
    DeconstructCred(cred)
    session = smtplib.SMTP('smtp.gmail.com', 587)
    session.ehlo()
    session.starttls()
    session.ehlo()
    session.login('char0program@gmail.com', 'pass123456789')
    body="Dear "+ cred[i]["name"] +" " + cred[i]["familyname"] + "\nYour password is: "+ cred[i]["pass"]
    headers=["from: " + 'char0program@gmail.com',  "subject: " + "Register to D&M chat", "to: " + result,"content-type: text/html"]
    headers="\r\n".join(headers)
    try:
        session.sendmail('char0program@gmail.com', result,headers+"\r\n\r\n" +body)
        conn.send(buildMsg("success"))
        
    except :
        conn.send(buildMsg("wrong"))

    
def Menu(cred, conn, result):
    #
    #    chat function
    #
    #
    
    e = 1
    r = 0
    registeredUsers = []
    friends = []
    friendList = InitFriendList()
    #pdb.set_trace()
    msg = ""
    newPassword = "" 
    i = 0 #where the email located in the file important
    j = 0
    k= 0 #for the registered users
    while 1:
        try:
            if(cred[i]["email"] == result):
                break
        except:
            break
        i+=1
    
    #pdb.set_trace()    
    registeredUsers = Registereds(cred)
    #pdb.set_trace()    
    
    #pdb.set_trace()
    conn.send(buildMsg("206")) #send the friend list
    

    if(result in friendList.keys()):
        sendFriend(conn, result, friendList)
        friends = friendList.values()[0].keys()
        GetMessageQueue(result)    
    
    if not friendList:
        conn.send(buildMsg("0"))
        
    
    #pdb.set_trace()
    while True:
        data = ""
        #sendSpaces(206)

        
        data = conn.recv(1000)
        rec_end = data.find("\r\n")
        if rec_end != -1:
            msg = data[:rec_end]
            
        #pdb.set_trace()
        if msg == '107':#If the client ask new password
            data = ""
            #enter your currently password 
            
            data = conn.recv(1000)
            rec_end = data.find("\r\n")
            if rec_end != -1:
                oldPassword = data[:rec_end]
            
           #pdb.set_trace()   
            if(cred[i]["pass"] == oldPassword): #check if the current passsword that entered is the real password
                conn.send(buildMsg("correct"))
                #if yes
                data = conn.recv(1000)
                #pdb.set_trace()
                rec_end = data.find("\r\n")
                if rec_end != -1:
                    newPassword = data[:rec_end]
                
                if(newPassword != "" and newPassword > 8):
                    #pdb.set_trace()
                    cred[i]["pass"] = newPassword
                    #Handle multiple write to the database
                    DeconstructCred(cred)
                    
                    
                    
                else:
                    conn.send(buildMsg("wrong"))
                    
            else:
                conn.send(buildMsg("wrong"))
                    
        
        if msg == '102': #Friend add
            
            data = conn.recv(1000)
            rec_end = data.find("\r\n")
            if rec_end != -1:
                friendEmail = data[:rec_end]
 
            
            if friendEmail in friends:
                conn.send(buildMsg("invalid"))
                break
                
            if friendEmail not in registeredUsers:
                conn.send(buildMsg("wrong"))
                break
                
              
            while e!=0:
                try:
                    if(cred[r]["email"] == friendEmail):
                        e = 0
                         
                except:
                    e = 0 
                    
                r+=1
            else:
                #pdb.set_trace()
                try:
                    friendList[result][friendEmail] = cred[r-1]["name"]
                    friendList[friendEmail][result] = cred[i]["name"]
                
                except KeyError, IndexError: #In caase the friend list empty 
                    friendList[result]= {friendEmail: cred[r-1]["name"]}
                    friendList[friendEmail]= {result: cred[i]["name"]}
                    
                conn.send(buildMsg("success"))    
                
                conn.send(buildMsg("206"))
                conn.send(buildMsg(friendEmail))
                conn.send(buildMsg(cred[r-1]["name"]))
                conn.send(buildMsg("0"))
                
                DeconstructorFriend(friendList) #fix the function
                print "Friend Added Seccessfully"
            
            
        if msg == '104': #SEND MESSAGE
            data = conn.recv(1000)
            rec_end = data.find("\r\n")
            if rec_end != -1:
                email = data[:rec_end]
            

            if email not in registeredUsers:
                conn.send(buildMsg("wrong")) #Send 204 when email not in the friendList or unregistered(should exit)
                break    
                
            data = conn.recv(1000)
            rec_end = data.find("\r\n")
            if rec_end != -1:
                message = data[:rec_end]
            
            #pdb.set_trace()
            ret = sendMessage(result,email,message,conn)
            '''
            if ret == 0:    
                conn.send(buildMsg("204"))
                break
               
            
            if ret == 1:
                conn.send(buildMsg("212")) #The message has been seccessfully sent to the target
                ##socket.timeout == check if the socket dead 
            '''
        '''
        if msg == '108':
            ret = Log(result, conn)
            if ret==0:
                conn.send("209")
                
        '''        
        
def DeconstructorFriend(friendList):
    fp = open("C:\\Users\\Dvir\\Dropbox\\chat\\friendlist.txt", "w")
    fp.write(str(friendList))
    fp.close()

def message_queue(message):
        result = "".join(message.keys())
        messages[message[result]["Reciever"]]= {result: message[result]["Message"]}
        
        
def GetMessageQueue(username):
    #pdb.set_trace()
    if messages:
        msgfor = "".join(messages.keys())
        thesender = "".join(messages[msgfor].keys())
        message = messages[msgfor][thesender]
        
        if username in messages.keys():
            UsrSock[username].send(buildMsg("205"))
            UsrSock[username].send(buildMsg(thesender))
            UsrSock[username].send(buildMsg(message))
        del messages[username]
    else:
        return 
        
        
def sendMessage(result, email, message, conn):
    messages = {}
    messages[result]  = {"Reciever": email, "Message": message, "Time": strftime("%Y-%m-%d %H:%M:%S", gmtime())}
    '''
    ret = DeConstructorMsg(message) #LOGS
    if ret == 0:
        #prevent denial of service the client's computer
        return 0
   # msg = InitMessage()
    '''    
    if email in UsrSock.keys():
        try:
            UsrSock[email].send(buildMsg("205"))
            UsrSock[email].send(buildMsg(result))
            UsrSock[email].send(buildMsg(message))
            return 1
            
        except socket.timeout:
            message_queue(messages)
            conn.send(buildMsg("207")) #When the friend is offline
    else:
        message_queue(messages)
        conn.send(buildMsg("207"))
        
        
def DeConstructorMsg(msg):
    filesize = os.stat("C:\\Users\\Dvir\\Dropbox\\chat\\msg.txt")
    if filesize.st_size > 10000:
        return 0
        
    fp = open("C:\\Users\\Dvir\\Dropbox\\chat\\msg.txt", "w")
    fp.write(str(msg)+",")
    fp.close()
  
''' 
def Log(result, conn):
    msg = {}
    fp = open("C:\\Users\\Dvir\\Dropbox\\chat\\msg.txt", "r")
    line = fp.readline()
    try:
        msg = ast.literal_eval(line)
        
        if result in msg.keys():
            for k, v msg[result].iteritems():
                conn.send(buildMsg(result))
                conn.send(buildMsg(msg[k]))
                conn.send(buildMsg(msg[v]))
                
            conn.send(buildMsg("0"))
            
    except SyntaxError:
        return 0 #The user doesnt have any log
    
  '''  

def InitCredential():
    cred = {}
    #pdb.set_trace()
    fp = open("C:\\Users\\Dvir\\Dropbox\\chat\\cred.txt", "r")
    line = fp.readline()   
    fp.seek(fp.tell())
    try:
        cred = ast.literal_eval(line)
        
    except SyntaxError:
        return cred
    
    return cred
  
def DeconstructCred(cred):
    #pdb.set_trace()
    
    fp = open("C:\\Users\\Dvir\\Dropbox\\chat\\cred.txt", "r+")
    if fp.readline() == '':    
        fp.seek(fp.tell())
        fp.write(str(cred)+ ",")
    else: 
        fp.seek(fp.tell())
        fp.write(str(cred)+ ",")
    fp.close()
    
def InitFriendList():
    friendList = {}
    #pdb.set_trace()
    fp = open("C:\\Users\\Dvir\\Dropbox\\chat\\friendlist.txt", "r")
    line = fp.readline()   
    fp.seek(fp.tell())
    try:
        friendList = ast.literal_eval(line)
        
    except SyntaxError:
        return friendList
    
    return friendList
   
def Authentication(cred, password, email, conn):
    #Return 1 if authentication seccesed and 0 if not
    #
    # 
    i = 0
    #pdb.set_trace()
    while 1:
        #pdb.set_trace()
        try:
            if (cred[i]["email"] == email and cred[i]["pass"] == password): 
                UsrSock[email] = conn
                return email
            i+=1
        except : #IndexError, KeyError
            return 0
        
def Deconstructor(conn):
    conn.close()
    
def handleclient(connection):   
        result = ""
        #pdb.set_trace()
        cred = InitCredential()
        isConnected = False   
        try:              
            while isConnected == False:##if he have not connected already
                #pdb.set_trace()
                data = ""
                rec = ""
                try:
                    data = connection.recv(1000)
                    rec_end = data.find('\r\n')
                    if rec_end != -1:
                        rec = data[:rec_end]
                        
                
                except socket.error:
                    print "Socket Error"
                    Deconstructor(connection)
                    break
                    
                    
                if rec == "101": #login
                    
                    print "Login process.."
                    data = ""
                    #pdb.set_trace()
                    #connection.send("\r\n>> Email: ")
                   
                    data = connection.recv(1000)
                    rec_end = data.find('\r\n')
                    if rec_end != -1:
                        email = data[:rec_end]
                        
            
                    data = ""
                    #connection.send(">> Password: ")
                    
                    data=connection.recv(1000)
                    rec_end = data.find("\r\n")
                    if rec_end != -1:
                        password = data[:rec_end]

                    result = Authentication(cred, password, email, connection)
                    #pdb.set_trace()

                    
                    if(result == 0):
                        print "Login failed"
                        connection.send(buildMsg("wrong"))
                        continue
                    
                    else:
                        print "Logged!"
                        connection.send("success")
                        isConnected = True
                        
                        continue
                        
                if rec == "103": #reset password
                
                    data=connection.recv(1000)
                    rec_end = data.find("\r\n")
                    if rec_end != -1:
                        emailReset = data[:rec_end]
                        
                    resetPassword(cred, connection, emailReset)
                    
                if rec == "100": #register
                    data = ""
                    email = ""
                    print "Registering process..."
                   #                   connection.send("\r\n-=Register-=")
                    
                  # connection.send("\r\n>> Name: ")
                    
                    data = connection.recv(1000)
                    rec_end = data.find('\r\n')
                    if rec_end != -1:
                        name = data[:rec_end]

            
                    data = ""
                  
#                  connection.send(">> familyname: ")
                    
                    data=connection.recv(1000)
                    rec_end = data.find("\r\n")
                    if rec_end != -1:
                        familyname = data[:rec_end]
                        
                            
                    data = ""
                   # connection.send(">> email: ")
                    
                    data=connection.recv(1000)
                    rec_end = data.find("\r\n")
                    if rec_end != -1:
                        email = data[:rec_end]
                        

                    #pdb.set_trace()  
                    result = Register(cred,name,familyname,email, connection)
                    #pdb.set_trace()
                    if(result == 0):
                        connection.send(buildMsg("wrong"))
                        continue
                        
                    if(result == 2):
                        connection.send(buildMsg("invalid"))
                        continue
                    else:
                        print "Registerd Seccessfully!"
                        connection.send(buildMsg("success"))
                        continue
                        
            Menu(cred, connection, result)
            
        except socket.error:
            print "error"
            Deconstructor(connection)
            
def main():
    while True:
        connection, addr = sckobj.accept()
        thread.start_new(handleclient, (connection,))
        #thread.start_new(checkforConnection, (connection,))
                

          
if __name__=='__main__':
       main() 